package org.beynet.docs.sync;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutorService;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.FileEntity;
import org.apache.log4j.Logger;
import org.beynet.docs.exceptions.GDocsServerError;
import org.beynet.docs.exceptions.UnexpectedStatusCode;
import org.beynet.docs.format.DocumentListEntry;
import org.beynet.docs.http.GDocsHTTPConstants;
import org.beynet.docs.http.HttpHelper;
import org.beynet.docs.http.HttpResumableUploader;
import org.beynet.docs.session.Session;


/**
 * upload a file : depending of its size it will create, or not, a resumable upload session.
 * @author beynet
 *
 */
public class FileUploaderHelper {

    /**
     * return the mime type of a document
     * @param file
     * @return
     * @throws IOException
     */
    public static String getMimeType(File file) throws IOException {
        if (!file.exists()) throw new IOException("File "+file.getPath()+" does not exit");
        String mimeType =  null ;

        // try to make a mime type by hand
        int index = file.getName().lastIndexOf(".") ; 
        if (index>=0) {
            mimeType = "application/"+file.getName().substring(index+1);
        }
        else {
            mimeType = "text/plain";
        }

        return(mimeType);
    }
    
    /**
     * update a file :<ul>
     * <li>if file size &lt;user provided httpChunkSize : the file is updated</li>
     * <li>else current file is removed from google servers and the new one is uploaded using resumable
     * uploads feature</li>
     * @param session         : user session
     * @param fileToSync      : the file to be synced
     * @param entry           : the entry describing the file on the remote side
     * @param remoteDirectory : the target directory on the remote side
     * @param resumable       : uri to create a new resumable upload in the target directory
     * @param executor
     * @param listener        : listener used to display file upload status
     * @param httpChunkSize   : the user provide httpChunkSize
     * @throws IOException
     * @throws GDocsServerError
     * @throws UnexpectedStatusCode
     */
    public static void updateFile(Session session,FileToSync fileToSync,DocumentListEntry entry,URI remoteDirectory,
                                  URI resumable,ExecutorService executor,Integer httpChunkSize) throws IOException, GDocsServerError, UnexpectedStatusCode {
        String filePath = fileToSync.getFile().getPath();
        File file = new File(filePath);
        if (file.length()<=httpChunkSize.intValue()) {
            logger.info("updating file "+filePath);
            fileToSync.startUpload();
            try {
                HttpPut updateFile = HttpHelper.createPutRequest(session, entry.getEditMediaLink().getURI().toString());
                updateFile.addHeader("If-Match",entry.getEtag());
                updateFile.setEntity(new FileEntity(file, getMimeType(file)));
                HttpResponse resp = HttpHelper.getHttpClient().execute(updateFile);
                HttpEntity respEntity = resp.getEntity();
                InputStream is = (respEntity!=null)?respEntity.getContent():null;
                int status ;
                try {
                    status = resp.getStatusLine().getStatusCode();
                    if (status>=200 && status<300) {
                        fileToSync.endUpload();
                        return;
                    }
                    if (status>=400 && status<500) throw new UnexpectedStatusCode(status);
                } finally {
                    if (is!=null) is.close();
                }
                logger.error("Retry request after receiving http status code="+status);
                try {
                    // retry the query after sleeping 1 sec
                    // ------------------------------------
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    
                }
                resp = HttpHelper.getHttpClient().execute(updateFile);
                respEntity = resp.getEntity();
                is = (respEntity!=null)?respEntity.getContent():null;
                try {
                    status = resp.getStatusLine().getStatusCode();
                    if (status>=200 && status<300) {
                        fileToSync.endUpload();
                        return;
                    }
                    throw new UnexpectedStatusCode(status);
                } finally {
                    if (is!=null) is.close();
                }
            } catch(IOException e) {
                fileToSync.uploadFailed();
                throw e;
            } catch(UnexpectedStatusCode e) {
                fileToSync.uploadFailed();
                throw e;
            }
        }
        else {
            logger.info("File size is greater than "+httpChunkSize+" make a resumable update url="+resumable.toString());
            entry.delete(session);
            uploadFile(session, fileToSync, remoteDirectory, resumable, executor, httpChunkSize);
        }
    }


    /**
     * upload a new file to servers
     * @param session
     * @param file
     * @param destination
     * @return
     * @throws IOException
     * @throws UnexpectedStatusCode
     */
    private static URI postNewFile(Session session,File file,URI destination) throws IOException, UnexpectedStatusCode {
        return(session.postNewFile(file, getMimeType(file), destination));
    }

    /**
     * upload a new file to servers
     * @param session
     * @param filepath
     * @param remoteDirectory
     * @param resumable
     * @param executor
     * @param httpChunkSize
     * @return
     * @throws IOException
     * @throws UnexpectedStatusCode
     */
    public static URI uploadFile(Session session,FileToSync fileToSync,URI remoteDirectory, URI resumable, ExecutorService executor,Integer httpChunkSize)
    throws IOException, UnexpectedStatusCode  {
        String filepath = fileToSync.getFile().getPath();
        File file = new File(filepath);

        if (remoteDirectory==null) {
            logger.info("uploading "+filepath+" to root folder");
            try {
                remoteDirectory = new URI(GDocsHTTPConstants.LIST_MAIN);
            } catch (URISyntaxException e) {
                throw new RuntimeException("unexpected error when creating uri from constant string");
            }
        }
        else {
            logger.info("uploading "+filepath+" to "+remoteDirectory.toString());
        }

        if (file.length()<=httpChunkSize.intValue()) {
            /*URI result = null ;
            fileToSync.startUpload();
            try {
                try {
                    result = postNewFile(session, file,remoteDirectory);
                    fileToSync.endUpload();
                } catch(Exception e) {
                    //retry once
                    result = postNewFile(session, file,remoteDirectory);
                    fileToSync.endUpload();
                }
                return(result);
            } finally {
                if (result==null) fileToSync.uploadFailed();
            }*/
            HttpResumableUploader uploader = new HttpResumableUploader(fileToSync, resumable, session, httpChunkSize,false);
            try {
                uploader.call();
                return(null);
            } catch (Exception ex) {
                throw new IOException(ex);
            }
        }
        else {
            HttpResumableUploader uploader = new HttpResumableUploader(fileToSync, resumable, session, httpChunkSize,false);
            executor.submit(uploader);
            return(null);
        }
    }
    
    private static final Logger logger = Logger.getLogger(FileUploaderHelper.class);

}
